#include "pathStack.h"

//初始化栈
int path_stack_init(path_stack_t* path_stack){
    path_stack->top = -1;
    path_stack_push(path_stack,"/");
    return 0;
}
//使用一个已经存在的栈，初始化另一个栈                        
int path_stack_copy(path_stack_t* new_path_stack,path_stack_t* old_path_stack){
    new_path_stack->top = -1;
    for(int i=0; i<=old_path_stack->top; i++){
        path_stack_push(new_path_stack,old_path_stack->stack[i]);
    }
    return 0;
}   
//销毁栈
int path_stack_destroy(path_stack_t* path_stack){
    while(path_stack->top >= 0){
        path_stack_pop(path_stack);
    }
    return 0;
}

//出栈
int path_stack_pop(path_stack_t* path_stack){
    if(path_stack->top == -1){
        //栈里已经没有元素了
        printf("stack is empty cannot pop\n");
        return -1;
    }
    free(path_stack->stack[path_stack->top]);
    --path_stack->top;
    return 0;
}

//入栈,入栈current_path_name
int path_stack_push(path_stack_t* path_stack, const char* current_path_name){
    ++path_stack->top;
    path_stack->stack[path_stack->top] = (char*)calloc(1,strlen(current_path_name)+1);
    strcpy(path_stack->stack[path_stack->top],current_path_name);
    return 0;
}

//获取当前路径,结果放到result
int get_current_path_on_server(path_stack_t* path_stack,char* result){
    for(int i = 0; i <= path_stack->top ; i++){
        strcat(result,path_stack->stack[i]);
        if(i != 0){
            strcat(result,"/");//添加路径分隔符
        }
        /* print_stack(path_stack); */
    }
    return 0;
}

//解析路径
int parse_path(path_stack_t* path_stack,const char* server_path, const char* full_path){
    char *token;
    char *temp_path = (char*)calloc(1,strlen(full_path)+1);
    strcpy(temp_path,full_path);
    char *save_state_strtok_r;//用来保存strtok_r的状态
    char current_real_path[1024] = {0};

    token = strtok_r(temp_path,"/",&save_state_strtok_r);
    while(token != NULL){
        /* printf("token:%s\t",token); */
        if(strcmp(token,"..") == 0){
            if(path_stack->top > 0){
                //不能把用户的根目录弹出来
                path_stack_pop(path_stack);
            }else{//目录不存在
                return -2;
            }
        }else if(strcmp(token,".") != 0 && strcmp(token," ") != 0){
            char temp_path[100] = {0};
            get_current_path_on_server(path_stack,temp_path);
            sprintf(current_real_path,"%s%s%s",server_path,temp_path,token);
            /* printf("--current_real_path:%s--\t",current_real_path); */
            if(access(current_real_path,F_OK) != 0){//目录不存在
                return -1;
            }
            path_stack_push(path_stack,token);
        }
        //继续切分
        token = strtok_r(NULL,"/",&save_state_strtok_r);
    }

    free(temp_path);
    return 0;
}

//cd命令
int cd_func(path_stack_t* path_stack, const char* server_path, const char *path, char* result){
    path_stack_t back_path;
    path_stack_copy(&back_path,path_stack);

    int retv = parse_path(path_stack,server_path,path);
    if(retv == -1){
        path_stack_destroy(path_stack);
        path_stack_copy(path_stack,&back_path);
        sprintf(result,"cd 失败: %s\n", strerror(errno));
    }
    else if(retv == -2){
        path_stack_destroy(path_stack);
        path_stack_copy(path_stack,&back_path);
        sprintf(result,"cd 失败: %s\n", "路径输入有误");
    }
    else{
        get_current_path_on_server(path_stack,result);
    }
    /* print_stack(path_stack); */
    path_stack_destroy(&back_path);
    return 0;
}

//pwd命令
int pwd_func(path_stack_t* path_stack, char* result){
    get_current_path_on_server(path_stack,result);
    return 0;
}

//输出栈里的所有内容
int print_stack(path_stack_t* path_stack){
    for(int i=0 ; i <= path_stack->top ; i++){
        printf("%s  -->  ",path_stack->stack[i]);
    }
    printf("\n");
    return 0;
}

//mkdir命令
int mkdir_func(path_stack_t* path_stack, const char* server_path, const char *dirname, char* result){
    char current_real_path[1024] = {0};
    strcat(current_real_path,server_path);
    get_current_path_on_server(path_stack,current_real_path);
    strcat(current_real_path,dirname);
    int retv = mkdir(current_real_path,0777);
    if(retv == 0){
        sprintf(result,"创建成功\n");
    }else{
        sprintf(result, "mkdir 失败: %s\n", strerror(errno));  // 输出错误信息
    }
    return 0;
}

//rmdir命令
int rmdir_func(path_stack_t* path_stack, const char* server_path, const char *dirname, char* result){
    char current_real_path[1024] = {0};
    strcat(current_real_path,server_path);
    get_current_path_on_server(path_stack,current_real_path);
    strcat(current_real_path,dirname);
    int retv = rmdir(current_real_path);
    if(retv == 0){
        sprintf(result,"删除成功\n");
    }else{
        sprintf(result, "rmdir 失败: %s\n", strerror(errno));  // 输出错误信息
    }
    return 0;
}

//remove命令
int remove_func(path_stack_t* path_stack, const char* server_path, const char *file_path, char* result){
    //3.删除当前路径下的文件
    char current_real_path[1024] = {0};
    strcat(current_real_path,server_path);                                                                             
    get_current_path_on_server(path_stack,current_real_path);
    strcat(current_real_path,file_path);
    /* printf("!!current_path_name:%s!!\n",current_real_path); */

    int retv = unlink(current_real_path);
    if(retv == 0){
        sprintf(result,"删除成功\n");
    }else{
        sprintf(result, "remove 失败: %s\n", strerror(errno));  // 输出错误信息
    }
    return 0;
}

//ls命令
int ls_func(path_stack_t* path_stack, const char* server_path, const char *dir_path, char* result){
    char current_real_path[1024] = {0};
    strcat(current_real_path,server_path);
    get_current_path_on_server(path_stack,current_real_path);
    strcat(current_real_path,dir_path);
    /* printf("!!current_path_name:%s!!\n",current_real_path); */

    DIR *dirp = opendir(current_real_path);
    if(dirp == NULL){
        sprintf(result,"ls 失败: %s\n",strerror(errno));
        return 0;
    }

    struct dirent* dir_entry = readdir(dirp);
    char* p = result;//为了方便偏移，拼接最终结果
    while ((dir_entry = readdir(dirp)) != NULL) {
        // 忽略 "." 和 ".."
        if (strcmp(dir_entry->d_name, ".") == 0 || strcmp(dir_entry->d_name, "..") == 0) {
            continue;
        }

        /* printf("--current name:%s--\n",dir_entry->d_name); */
        int written_num = sprintf(p,"%s   ",dir_entry->d_name);
        p += written_num;
    }
    closedir(dirp);
    return 0;
}

// 设置文件类型和权限字符串
void set_type_mode(mode_t mode, char *tm_str){
    // 处理第一个字符，即文件类型
    switch (mode & S_IFMT) {
    case S_IFBLK:   tm_str[0] = 'b';        break;
    case S_IFCHR:   tm_str[0] = 'c';        break;
    case S_IFDIR:   tm_str[0] = 'd';        break;
    case S_IFIFO:   tm_str[0] = 'p';        break;
    case S_IFLNK:   tm_str[0] = 'l';        break;
    case S_IFREG:   tm_str[0] = '-';        break;
    case S_IFSOCK:  tm_str[0] = 's';        break;
    default:        tm_str[0] = '?';        break;
    }

    // 处理后续九个字符，即文件的权限信息
    // 设置拥有者的权限信息
    tm_str[1] = (mode & 0400) ? 'r' : '-';
    tm_str[2] = (mode & 0200) ? 'w' : '-';
    tm_str[3] = (mode & 0100) ? 'x' : '-';
    // 设置拥有者组的权限
    tm_str[4] = (mode & 0040) ? 'r' : '-';
    tm_str[5] = (mode & 0020) ? 'w' : '-';
    tm_str[6] = (mode & 0010) ? 'x' : '-';
    // 设置其他人的权限
    tm_str[7] = (mode & 0004) ? 'r' : '-';
    tm_str[8] = (mode & 0002) ? 'w' : '-';
    tm_str[9] = (mode & 0001) ? 'x' : '-';
    tm_str[10] = '\0'; // 确保字符串以 null 结尾
}

// 获取格式化的时间字符串
void set_time(time_t mtime, char *time_str){
    // 由于tm结构体中存储的是月份的整数值，我们需要的是月份字符串，所以用一个字符串数组来存储月份字符串
    const char month_arr[][10] = {
        "1月", "2月", "3月", "4月", "5月", "6月",
        "7月", "8月", "9月", "10月", "11月", "12月"
    };  // tm结构体当中的月份范围是[0, 11]，刚好可以适配这个数组

    // 调用localtime函数，获取tm结构体指针
    struct tm* st_tm = localtime(&mtime);
    // 构建时间字符串,格式为：月份 天数 时：分
    sprintf(time_str, "%s %2d %02d:%02d",
            month_arr[st_tm->tm_mon],
            st_tm->tm_mday,
            st_tm->tm_hour,
            st_tm->tm_min);
}

// 将传入的字符串字母都转换成小写字母  
void str_to_lower(char *str) {
    while (*str) {
        *str = tolower(*str);
        str++;
    }
}
// 用文件名的字典顺序，排序目录项结构体指针数组
int compare(const void* a, const void* b){
    // 此时a和b都是指向目录项结构体指针的指针，所以它们是二级指针，需要强转再解引用
    struct dirent* dir_a = *(struct dirent**)a;
    struct dirent* dir_b = *(struct dirent**)b;
    // 将文件名都转换成小写字母然后再按字典顺序排序 
    // 原始文件名不能被修改，所以创建临时数组来存储文件名的副本
    char file_name[256];
    char file_name2[256];

    strncpy(file_name, dir_a->d_name, sizeof(file_name) - 1);
    strncpy(file_name2, dir_b->d_name, sizeof(file_name2) - 1);

    // 确保字符串以空字符终止
    file_name[sizeof(file_name) - 1] = '\0';
    file_name2[sizeof(file_name2) - 1] = '\0';

    // 将副本转换为小写
    str_to_lower(file_name);
    str_to_lower(file_name2);

    // 返回副本文件名的字典顺序
    return strcmp(file_name, file_name2);
}

//2.0有排序功能的ll命令
int ll_func(path_stack_t* path_stack, const char* server_path, const char *dir_path, char* result){
    char current_real_path[1024] = {0};
    strcat(current_real_path,server_path);
    get_current_path_on_server(path_stack,current_real_path);
    strcat(current_real_path,dir_path);
    /* printf("!!current_path_name:%s!!\n",current_real_path); */

    DIR *dirp = opendir(current_real_path);
    if(dirp == NULL){
        sprintf(result,"ll 失败: %s\n",strerror(errno));
        return 0;
    }
    // 遍历目录项构建dirent结构体数组，用于实现排序
    // 为了更好的排序性能，选择使用结构体指针数组，而不是结构体数组
    // 为了程序能够更加灵活使用，选择动态内存分配这个结构体指针数组
    // 先遍历一次计算目录项的个数
    int count = 0;
    struct dirent* dp;

    // 第一次遍历：计算目录项数量
    while ((dp = readdir(dirp)) != NULL) {
        count++;
    }   // while循环结束时,dp是一个空指针,count就是目录项的个数

    // 动态申请结构体指针数组
    struct dirent** dir_arr = (struct dirent**)malloc(sizeof(struct dirent*) * count);
    if(dir_arr == NULL){
        ERROR_CHECK(dir_arr, NULL, "malloc dlist");
        return -1;
    }

    // 倒带目录流指针
    rewinddir(dirp);

    // 第二次遍历: 将目录项结构体对象指针存入数组中
    int idx = 0;
    while((dp = readdir(dirp)) != NULL){
        dir_arr[idx] = dp;
        idx++;
    }   // while循环结束时，待打印目录下的所有文件的dirent结构体指针斗被存入了dir_arr数组

    //  按照名字的字典顺序，利用qsort函数排序dir_arr数组
    qsort(dir_arr, count, sizeof(struct dirent *), compare);
    char* p = result;
    /* printf("count = %d\n",count); */
    // 最后一步：根据已排序的目录项指针数组，获取stat结构体，打印文件详细信息
    for(int i = 0; i < count; i++){
        struct stat sb;
        // 文件名不再从dirent结构体中获取，不要忘记修改

        char full_path[2048];
        sprintf(full_path,"%s/%s",current_real_path,dir_arr[i]->d_name);
        int ret = stat(full_path, &sb);
        if(ret == -1){
            sprintf(result,"ll 失败: %s\n",strerror(errno));
            return 0;
        }
        // 1.处理stat的成员st_mode，将它转换成权限和类型字符串
        char tm_str[1024] = {0};
        set_type_mode(sb.st_mode, tm_str);

        // 2.获取用户名和组名
        char *username = getpwuid(sb.st_uid)->pw_name;
        char *gname = getgrgid(sb.st_gid)->gr_name;

        // 3.将时间戳转换为时间字符串
        char time_str[1024] = {0};
        set_time(sb.st_mtim.tv_sec, time_str);

        int written_num = sprintf(p,"%s %2lu %s %s %6lu %s %s\n",
                                  tm_str,
                                  sb.st_nlink,
                                  username,
                                  gname,
                                  sb.st_size,
                                  time_str,
                                  // 文件名不再从dirent结构体中获取，不要忘记修改
                                  dir_arr[i]->d_name);
        p += written_num;
    }

    // 不要忘记free动态申请的目录项结构体指针数组
    free(dir_arr);

    // readdir返回的目录流结构体内存是由目录流自身管理的，会随着关闭目录流而自动释放
    // 所以如果在上面关闭目录流，极有可能导致目录项结构体被释放，从而导致数据漏输出
    closedir(dirp);
    return 0;
}
